Generally we mean using code to verify that other code is working as expected.
Unit testing - do your functions and methods behave correctly?
Integration testing - do your units integrate together correctly?
End to end testing - does your final analytical pipeline work as expected?
Note:
Tests are nothing more than a comparison between an expectation you have about
your code and the actual result you obtain. If the two match up, your test
passes, if not, your test fails.
Some languages have testing functionality built in to them (e.g. Rust) others
rely on external frameworks (e.g. R).
No need to use a testing framework for this but they can make your life
easier.
What are the benefits of testing?
Reassurance - your code does what you think it does.
Robustness - allows you to catch newly introduced bugs when adding new
features or during refactoring.
Code structure - encourages a more modular approach to code. Writing tests for
smaller, well structured functions is much easier than larger ones that do a
lot.
Additional documentation - a test suite can act as an accompaniment to
documentation.
Maintainability - as part of a continuous integration setup you can
easily check that your code will run with the latest version of your chosen
language and your code dependencies.
Easy life - We test to make our own life (and others) easier!
What to test?
Let's restrict ourselves to two scenarios you are most likely to encounter:
writing functionality (e.g. libraries / packages) for others to use; and
workflows (in whatever form they come) for our own analysis.
Functionality for others (test the surface)
Our own analysis
If you have trust in your dependencies (why use them if not) then there is
no need to test functions you use from those packages.
Complexity of your functions can be a good gauge of where to test. However,
even with “simple” functions, you can easily make mistakes.
When you think your workflow is working as desired, snapshot the end
result to ensure future revisions to not alter this output.